package org.baicaixiaozhan.tenet.freemarker.util;

import cn.hutool.core.io.FileUtil;
import com.google.common.annotations.Beta;
import com.google.common.io.ByteStreams;
import com.spire.doc.Document;
import com.spire.doc.FileFormat;
import com.spire.doc.ProtectionType;
import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.SneakyThrows;
import org.apache.commons.io.FileUtils;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

/**
 * DESC:
 *
 * @author baicaixiaozhan
 * @since 1.0.0
 */
public class DefaultFreeMarkFile implements FreeMarkFile {

    private File tempFile;
    private String basePackagePath;
    private Boolean readOnly = false;
    private String password = "";

    public void setTempFile(File tempFile) {
        this.tempFile = tempFile;
    }

    public File getTempFile() {
        return tempFile;
    }

    public String getBasePackagePath() {
        return basePackagePath;
    }

    @Override
    public FreeMarkFile setBasePackagePath(String basePackagePath) {
        this.basePackagePath = basePackagePath;
        return this;
    }

    @SneakyThrows
    @Override
    public DocFileOpt buildDocFile(Map<String, String> data, String templateName) {
        File docFile = this.getDocFile(data, templateName);
        if (this.readOnly) {
            Document document = new Document(new FileInputStream(docFile));
            document.protect(ProtectionType.Allow_Only_Reading, this.password);
            document.saveToFile(new FileOutputStream(docFile), FileFormat.Doc);
        }
        this.setTempFile(docFile);
        return this;
    }

    @SneakyThrows
    @Override
    public <T> DocFileOpt buildDocFile(T data, String templateName) {
        File docFile = this.getDocFile(data, templateName);
        if (this.readOnly) {
            Document document = new Document(new FileInputStream(docFile));
            document.protect(ProtectionType.Allow_Only_Reading, this.password);
            document.saveToFile(new FileOutputStream(docFile), FileFormat.Doc);
        }
        this.setTempFile(docFile);
        return this;
    }

    @Override
    public DocFileOpt readOnlyDocFile(String password) {
        this.readOnly = Boolean.TRUE;
        this.password = password;
        return this;
    }

    @SneakyThrows
    @Override
    public DocxFileOpt buildDocxFile(Map<String, String> data, String template) {
        File docxFile = this.getDocxFile(data, template, Collections.emptyList());
        if (this.readOnly) {
            Document document = new Document(new FileInputStream(docxFile));
            document.protect(ProtectionType.Allow_Only_Reading, this.password);
            document.saveToFile(new FileOutputStream(docxFile), FileFormat.Docx);
        }
        this.setTempFile(docxFile);
        return this;
    }

    @SneakyThrows
    @Override
    public <T> DocxFileOpt buildDocxFile(T data, String template) {
        File docxFile = this.getDocxFile(data, template, Collections.emptyList());
        if (this.readOnly) {
            Document document = new Document(new FileInputStream(docxFile));
            document.protect(ProtectionType.Allow_Only_Reading, this.password);
            document.saveToFile(new FileOutputStream(docxFile), FileFormat.Docx);
        }
        this.setTempFile(docxFile);
        return this;
    }

    @SneakyThrows(FileNotFoundException.class)
    @Override
    public <T> DocxFileOpt buildDocxFile(T data, String template, List<File> imageList) {
        File docxFile = this.getDocxFile(data, template, imageList);
        if (this.readOnly) {
            Document document = new Document(new FileInputStream(docxFile));
            document.protect(ProtectionType.Allow_Only_Reading, this.password);
            document.saveToFile(new FileOutputStream(docxFile), FileFormat.Docx);
        }
        this.setTempFile(docxFile);
        return this;
    }

    @Beta
    @Override
    public DocxFileOpt readOnlyDocxFile(String password) {
        this.readOnly = Boolean.TRUE;
        this.password = password;
        return this;
    }

    @Override
    public File getFile() {
        return tempFile;
    }

    @Override
    public void deleteFile() {
        tempFile.deleteOnExit();
    }

    @SneakyThrows
    @Override
    public void convertDocToHtml(HttpServletResponse response) {
        // TODO 暂时不支持
        throw new UnsupportedOperationException();
    }

    @Override
    public void convertDocxToHtml(HttpServletResponse response) {
        // TODO 暂时不支持
        throw new UnsupportedOperationException();
    }

    @SneakyThrows
    private void downloadFile(String fileName, HttpServletResponse response, Boolean enableDocx) {
        // 以流的形式下载文件
        InputStream fis = new BufferedInputStream(new FileInputStream(tempFile));
        byte[] buffer = new byte[fis.available()];
        fis.read(buffer);
        fis.close();
        // 清空 response
        response.reset();
        // 设置 response 的 Header
        String fullFileName = URLEncoder.encode(enableDocx
                ? fileName + ".docx" : fileName + ".doc", "UTF-8");
        response.addHeader("Content-Disposition", "attachment;filename=" + fullFileName);
        response.addHeader("Content-Length", String.valueOf(tempFile.length()));
        OutputStream toClient = new BufferedOutputStream(response.getOutputStream());
        response.setContentType("application/octet-stream");
        toClient.write(buffer);
        toClient.flush();
        toClient.close();

        this.deleteFile();
    }

    @SneakyThrows
    @Override
    public void downloadDocFile(String fileName, HttpServletResponse response) {
        this.downloadFile(fileName, response, false);
    }

    @SneakyThrows
    @Override
    public void downloadDocxFile(String fileName, HttpServletResponse response) {
        this.downloadFile(fileName, response, true);
    }

    @SneakyThrows
    public <T> File getDocxFile(T data, String template, List<File> imageList) {
        if (getBasePackagePath().isEmpty()) {
            throw new NullPointerException("Field basePackagePath cannot be empty.");
        }

        // 创建配置实例
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_0);
        // 设置编码
        configuration.setDefaultEncoding("UTF-8");
        configuration.setClassForTemplateLoading(FreeMarks.class, getBasePackagePath());

        // 输出文件
        ClassPathResource resource = new ClassPathResource("wordTemplate/template_docx.docx");
        InputStream inputStream = resource.getInputStream();
        File docxFile = File.createTempFile("temp_file", ".docx");
        FileUtils.copyInputStreamToFile(inputStream, docxFile);

        ZipOutputStream zipOut = null;
        OutputStream outputStream = FileUtil.getOutputStream(docxFile);
        try {
            // 内容
            ByteArrayInputStream inputStreamOfContent = getFreemarkerContentInputStream(configuration, data, "document.xml");
            // 图片
            ByteArrayInputStream inputStreamOfRels = getFreemarkerContentInputStream(configuration, data, "document.xml.rels");

            boolean replaceMedia = false;

            ClassPathResource originalTemplate = new ClassPathResource(getBasePackagePath().replaceFirst("/", "") + template);
            File outputFile = File.createTempFile("temp_docx_file_1_", ".docx");
            FileOutputStream out = new FileOutputStream(outputFile);
            ByteStreams.copy(originalTemplate.getInputStream(), out);

            ZipFile zipFile = new ZipFile(outputFile);
            Enumeration<? extends ZipEntry> zipEntrys = zipFile.entries();
            zipOut = new ZipOutputStream(outputStream);
            //开始覆盖文档------------------
            int len = -1;
            byte[] buffer = new byte[1024];
            while (zipEntrys.hasMoreElements()) {
                ZipEntry next = zipEntrys.nextElement();
                InputStream is = zipFile.getInputStream(next);
                String entryName = next.getName();
                zipOut.putNextEntry(new ZipEntry(entryName));
                if (entryName.indexOf("document.xml.rels") > 0) {
                    // 如果是document.xml.rels由我们输入
                    if (inputStreamOfRels != null) {
                        while ((len = inputStreamOfRels.read(buffer)) != -1) {
                            zipOut.write(buffer, 0, len);
                        }
                        inputStreamOfRels.close();
                    }
                } else if ("word/document.xml".equals(entryName)) {
                    // 如果是word/document.xml由我们输入
                    if (inputStreamOfContent != null) {
                        while ((len = inputStreamOfContent.read(buffer)) != -1) {
                            zipOut.write(buffer, 0, len);
                        }
                        inputStreamOfContent.close();
                    }
                } else if (entryName.contains("word/media/") && replaceMedia) {
                } else if (entryName.contains("word/media/") && !"word/media/".equals(entryName) && !replaceMedia) {
                    replaceMedia = true;

                    if (!CollectionUtils.isEmpty(imageList)) {
                        for (File file : imageList) {
                            String fileName = file.getName();
                            FileInputStream in = new FileInputStream(file);
                            if (entryName.contains(fileName)) {
                                while ((len = in.read(buffer)) != -1) {
                                    zipOut.write(buffer, 0, len);
                                }
                            } else {
                                zipOut.putNextEntry(new ZipEntry("word/media/" + fileName));
                                while ((len = in.read(buffer)) != -1) {
                                    zipOut.write(buffer, 0, len);
                                }
                            }
                            in.close();
                        }
                    }
                } else {
                    while ((len = is.read(buffer)) != -1) {
                        zipOut.write(buffer, 0, len);
                    }
                    is.close();
                }
            }
        } catch (Exception e) {
            e.getStackTrace();
        } finally {
            if (zipOut != null) {
                try {
                    zipOut.close();
                } catch (IOException e) {
                    e.getStackTrace();
                }
            }
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.getStackTrace();
                }
            }
        }

        return docxFile;
    }

    /**
     * 获取模板字符串输入流
     *
     * @param configuration
     * @param data
     * @param templateName
     * @param <T>
     * @return
     */
    public static <T> ByteArrayInputStream getFreemarkerContentInputStream(Configuration configuration, T data, String templateName) {
        ByteArrayInputStream in = null;
        try {
            // 获取模板
            Template template = configuration.getTemplate(templateName);
            StringWriter swriter = new StringWriter();
            // 生成文件
            template.process(data, swriter);
            // 这里一定要设置utf-8编码 否则导出的word中中文会是乱码
            in = new ByteArrayInputStream(swriter.toString().getBytes(StandardCharsets.UTF_8));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return in;
    }

    @SneakyThrows
    private <T> File getDocFile(T data, String templateName) {
        if (getBasePackagePath().isEmpty()) {
            throw new NullPointerException("Field basePackagePath cannot be empty.");
        }

        // 创建配置实例
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_0);
        // 设置编码
        configuration.setDefaultEncoding("UTF-8");
        configuration.setClassForTemplateLoading(FreeMarks.class, getBasePackagePath());
        // 获取模板
        Template template = configuration.getTemplate(templateName);

        // 输出文件
        ClassPathResource resource = new ClassPathResource("wordTemplate/template_doc.doc");
        InputStream inputStream = resource.getInputStream();
        File file = File.createTempFile("temp_file", ".doc");
        FileUtils.copyInputStreamToFile(inputStream, file);

        // 将模板和数据模型合并生成文件
        Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), StandardCharsets.UTF_8));
        template.process(data, out);
        out.close();
        return file;
    }
}
